package org.tool.net;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.tool.io.IOUtil;
import org.tool.net.http.HttpMethod;
import org.tool.net.http.HttpResponse;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Http客户端工具类
 *
 * @author 李坤
 * @date 2022/8/31 14:40
 */
public abstract class HttpClient {

    /**
     * URL分隔符
     */
    private static final String URL_DELIMITER = "/";
    /**
     * 默认的请求头信息
     */
    private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>();

    static {
        DEFAULT_HEADERS.put("Content-Type", "application/json");
    }

    /**
     * 请求指定URL地址的接口，并返回响应状态码及响应内容
     *
     * @param addr URL地址
     *
     * @return 响应状态码及相应内容
     *
     * @throws IOException URL格式错误 或 URL无法访问
     */
    public static HttpResponse<String> get(String addr) throws IOException {
        return generateHttpResponse(getConnection(addr, HttpMethod.GET));
    }

    /**
     * 请求指定URL地址的接口，并返回响应状态码及响应内容
     *
     * @param addr URL地址
     *
     * @return 响应状态码及相应内容
     *
     * @throws IOException URL格式错误 或 URL无法访问
     */
    public static HttpResponse<String> post(String addr, Object parameter) throws IOException {
        return request(addr, parameter, HttpMethod.POST);
    }

    /**
     * 请求指定URL地址的接口，并返回响应状态码及响应内容
     *
     * @param addr  URL地址
     * @param clazz 需要转换的类型
     *
     * @return 响应状态码及相应内容
     *
     * @throws IOException URL格式错误 或 URL无法访问
     */
    public static <T> HttpResponse<T> post(String addr, Object parameter, Class<T> clazz) throws IOException {
        final HttpURLConnection connection = getConnection(addr, HttpMethod.POST);
        writeRequestParameter(connection, parameter);

        return generateHttpResponse(connection, clazz);
    }

    /**
     * 请求指定URL地址的接口，并返回响应状态码及响应内容
     *
     * @param addr  URL地址
     * @param clazz 需要转换的类型
     *
     * @return 响应状态码及相应内容
     *
     * @throws IOException URL格式错误 或 URL无法访问
     */
    public static <T> HttpResponse<T> post(String addr, Object parameter, TypeReference<T> clazz) throws IOException {
        final HttpURLConnection connection = getConnection(addr, HttpMethod.POST);
        writeRequestParameter(connection, parameter);

        return generateHttpResponse(connection, clazz);
    }

    /**
     * 请求指定URL地址的接口，并返回响应状态码及响应内容
     *
     * @param addr URL地址
     *
     * @return 响应状态码及相应内容
     *
     * @throws IOException URL格式错误 或 URL无法访问
     */
    public static HttpResponse<String> request(String addr, Object parameter, HttpMethod httpMethod) throws IOException {
        final HttpURLConnection connection = getConnection(addr, httpMethod);
        writeRequestParameter(connection, parameter);

        return generateHttpResponse(connection);
    }


    /*-------------------------------------------------private methods------------------------------------------------*/


    /**
     * 根据给定的URL获取 {@link HttpURLConnection} 对象
     *
     * @param addr URL地址
     *
     * @return {@link HttpURLConnection} 对象
     *
     * @throws IOException URL地址错误
     */
    private static HttpURLConnection getConnection(String addr) throws IOException {
        final URL url = new URL(addr);
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * 根据给定的URL获取 {@link HttpURLConnection} 对象，并设置请求方式
     *
     * @param addr       URL地址
     * @param httpMethod 请求方式
     *
     * @return {@link HttpURLConnection} 对象
     *
     * @throws IOException URL地址错误
     */
    private static HttpURLConnection getConnection(String addr, HttpMethod httpMethod) throws IOException {
        return getConnection(addr, httpMethod, null);
    }

    /**
     * 根据给定的URL获取 {@link HttpURLConnection} 对象，并设置请求方式
     *
     * @param addr       URL地址
     * @param httpMethod 请求方式
     * @param headers    请求头信息
     *
     * @return {@link HttpURLConnection} 对象
     *
     * @throws IOException URL地址错误
     */
    private static HttpURLConnection getConnection(String addr, HttpMethod httpMethod, Map<String, String> headers) throws IOException {
        final HttpURLConnection connection = getConnection(addr);
        connection.setRequestMethod(httpMethod.name());

        // 添加默认的请求头信息
        appendHeaders(connection, Objects.isNull(headers) ? Collections.emptyMap() : headers);

        return connection;
    }

    /**
     * 向请求中添加指定的及默认的响应头信息
     *
     * @param connection HTTP连接
     * @param headers    自定义请求头信息
     */
    private static void appendHeaders(HttpURLConnection connection, Map<String, String> headers) {
        if (ObjectUtil.isNotEmpty(headers)) {
            headers.forEach(connection::addRequestProperty);
        } else {
            DEFAULT_HEADERS.forEach(connection::addRequestProperty);
        }
    }

    /**
     * 根据给定的 {@link HttpURLConnection} 对象生成 {@link HttpResponse} 对象
     *
     * @param connection HTTP请求连接
     *
     * @return HTTP部分相应内容
     *
     * @throws IOException 获取相应内容错误
     */
    private static HttpResponse<String> generateHttpResponse(HttpURLConnection connection) throws IOException {
        final int state = connection.getResponseCode();

        try (final InputStream inputStream = connection.getInputStream()) {
            final String body = IOUtil.readStrUtf8(inputStream);
            return new HttpResponse<>(state, body);
        }
    }

    /**
     * 根据给定的 {@link HttpURLConnection} 对象生成 {@link HttpResponse} 对象
     *
     * @param connection HTTP请求连接
     * @param clazz      需要转换的类型
     *
     * @return HTTP部分相应内容
     *
     * @throws IOException 获取相应内容错误
     */
    private static <T> HttpResponse<T> generateHttpResponse(HttpURLConnection connection, Class<T> clazz) throws IOException {
        final int state = connection.getResponseCode();

        try (final InputStream inputStream = connection.getInputStream()) {
            final String str = IOUtil.readStrUtf8(inputStream);
            final T body = JSON.parseObject(str, clazz);
            return new HttpResponse<>(state, body);
        }
    }


    /**
     * 根据给定的 {@link HttpURLConnection} 对象生成 {@link HttpResponse} 对象
     *
     * @param connection HTTP请求连接
     * @param clazz      需要转换的类型
     *
     * @return HTTP部分相应内容
     *
     * @throws IOException 获取相应内容错误
     */
    private static <T> HttpResponse<T> generateHttpResponse(HttpURLConnection connection, TypeReference<T> clazz) throws IOException {
        final int state = connection.getResponseCode();

        try (final InputStream inputStream = connection.getInputStream()) {
            final String str = IOUtil.readStrUtf8(inputStream);
            final T body = JSON.parseObject(str, clazz);
            return new HttpResponse<>(state, body);
        }
    }

    /**
     * 向请求中添加请求内容
     *
     * @param connection HTTP连接
     * @param parameter  请求内容
     *
     * @throws IOException IO操作异常
     */
    private static void writeRequestParameter(HttpURLConnection connection, Object parameter) throws IOException {
        if (ObjectUtil.isNotEmpty(parameter)) {
            connection.setDoOutput(true);

            try (final OutputStream outputStream = connection.getOutputStream()) {
                if (parameter instanceof String) {
                    outputStream.write(((String) parameter).getBytes(StandardCharsets.UTF_8));
                } else {
                    outputStream.write(JSON.toJSONString(parameter).getBytes(StandardCharsets.UTF_8));
                }
            }
        }
    }

}
